/**
 * @author Vitor Franco do Carmo (vitor.carmo@cpmbraxis.com)
 * @since 03/08/2013
 *
 */
package br.com.vfcfw.test.functional;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.pagefactory.DefaultElementLocatorFactory;
import org.openqa.selenium.support.pagefactory.DefaultFieldDecorator;
import org.openqa.selenium.support.pagefactory.ElementLocatorFactory;
import org.openqa.selenium.support.pagefactory.FieldDecorator;

/**
 * Classe base para todas as classes que representam uma página do teste funcional.
 * 
 * @author Vitor Franco do Carmo (vitor.carmo@cpmbraxis.com)
 * @since 03/08/2013
 * 
 */
public class BasePagina {

    protected ComandosSelenium comandos;

    private static WebDriver driverAtual;

    /**
     * Construtor da classe.
     * 
     * @param driver {@link WebDriver}.
     */
    public BasePagina(WebDriver driver) {

        super();
        this.comandos = new ComandosSelenium(driver);
        driverAtual = driver;
    }

    /**
     * Retorna um proxy da página.
     * 
     * @param classePagina {@link Class} Classe da página.
     * @return Proxy da página.
     */
    public static <T> T obterProximaPagina(Class<T> classePagina) {

        WebDriver driver = driverAtual;
        T page = inicializarPagina(driver, classePagina);
        inicializarElementos(driver, page);
        return page;
    }

    /**
     * Inicializar os elementos da página.
     * 
     * @param driver {@link WebDriver}.
     * @param pagina Instância da página.
     */
    public static void inicializarElementos(WebDriver driver, Object pagina) {

        final WebDriver driverRef = driver;
        inicializarElementos(new DefaultElementLocatorFactory(driverRef), pagina);
    }

    /**
     * Inicializar os elementos da página.
     * 
     * @param factory {@link ElementLocatorFactory} Fábrica dos elementos da página.
     * @param pagina Instância da página.
     */
    public static void inicializarElementos(ElementLocatorFactory factory, Object pagina) {

        final ElementLocatorFactory factoryRef = factory;
        inicializarElementos(new DefaultFieldDecorator(factoryRef), pagina);
    }

    /**
     * Inicializar os elementos da página.
     * 
     * @param decorator {@link FieldDecorator} Decorador dos campos.
     * @param pagina Instância da página.
     */
    public static void inicializarElementos(FieldDecorator decorator, Object pagina) {

        Class<?> proxyIn = pagina.getClass();
        while (proxyIn != Object.class) {
            inicializarCamposPagina(decorator, pagina, proxyIn);
            proxyIn = proxyIn.getSuperclass();
        }
    }

    /**
     * Cria um proxy dos campos da página.
     * 
     * @param decorator {@link FieldDecorator} Decorador do campo.
     * @param pagina Instância da página.
     * @param classePagina {@link Class} Classe da página.
     */
    private static void inicializarCamposPagina(FieldDecorator decorator, Object pagina, Class<?> classePagina) {

        Field[] fields = classePagina.getDeclaredFields();
        for (Field field : fields) {
            Object value = decorator.decorate(pagina.getClass().getClassLoader(), field);
            if (value != null) {
                try {
                    field.setAccessible(true);
                    field.set(pagina, value);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * Inicializa a página retornando um proxy dela mesma.
     * 
     * @param driver {@link WebDriver}.
     * @param classePagina {@link Class} Classe que representa a página.
     * @return Proxy da página.
     */
    private static <T> T inicializarPagina(WebDriver driver, Class<T> classePagina) {

        try {
            try {
                Constructor<T> constructor = classePagina.getConstructor(WebDriver.class);
                return constructor.newInstance(driver);
            } catch (NoSuchMethodException e) {
                return classePagina.newInstance();
            }
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

}
